﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Forms;

using NantAssistant.EventArgs;
using NantAssistant.Util;

namespace NantAssistant
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        delegate void UpdateOutputDelegate(string output);

        private KeyPressHandler keyHandler;
        private NantExec nant;
        private NantFile parsedFile;
        private BackgroundWorker loadWorker;
        private BackgroundWorker runWorker;
        private int lastHighlightIndex;
        private bool errorFound;

        public MainWindow()
        {
            InitializeComponent();

            loadWorker = new BackgroundWorker();
            loadWorker.DoWork += loadWorker_DoWork;
            loadWorker.RunWorkerCompleted += loadWorker_RunWorkerCompleted;

            runWorker = new BackgroundWorker();
            runWorker.DoWork += runWorker_DoWork;
            runWorker.RunWorkerCompleted += runWorker_RunWorkerCompleted;

            lastHighlightIndex = -1;
            errorFound = false;
        }

        #region UI Events

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(Config.NantPath) || !File.Exists(Config.NantPath))
            {
                UpdateStatus(StatusType.Update, "You must set a location for NAnt before you will be able to run.");
                btnRun.IsEnabled = false;
                btnSetNant.Visibility = System.Windows.Visibility.Visible;
            }

            nant = new NantExec(Config.NantPath);
            nant.OutputDataReceived += nant_OutputDataReceived;
            nant.ErrorDataReceived += nant_ErrorDataReceived;

            keyHandler = new KeyPressHandler();
            foreach (var p in Config.Properties)
                AddProperty(p.Name, p.Value);

            // default selected file
            txtNantFile.Text = File.Exists(Config.DefaultFile) ? Config.DefaultFile : string.Empty;
            LoadTargets();
        }

        private void btnBrowse_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                using (OpenFileDialog openfileDialog = new OpenFileDialog())
                {
                    openfileDialog.Title = "Select a NAnt file to load";

                    DialogResult result = openfileDialog.ShowDialog();

                    if (result == System.Windows.Forms.DialogResult.OK)
                        txtNantFile.Text = openfileDialog.FileName.ToString();
                }
            }
            catch { }

            if (!string.IsNullOrEmpty(txtNantFile.Text))
                LoadTargets();
        }

        private void btnRun_Click(object sender, RoutedEventArgs e)
        {
            if (nant.Targets.Count == 0)
            {
                UpdateStatus(StatusType.RunError, "No targets selected.");
                return;
            }

            ToggleButtons(false);
            UpdateStatus(StatusType.Update, "Executing selected NAnt targets...");
            txtOutput.Text = ""; // clear output

            txtOutput.Text = ""; // clear out any old output
            pbProgress.Visibility = System.Windows.Visibility.Visible;
            tabOutput.IsSelected = true;

            // reset the border
            if (errorFound)
            {
                errorFound = false;
                brdrOutput.BorderBrush = System.Windows.Media.Brushes.Black;
            }

            runWorker.RunWorkerAsync(txtNantFile.Text);
        }

        private void btnSetNant_Click(object sender, RoutedEventArgs e)
        {
            using (var dialog = new OpenFileDialog())
            {
                dialog.Title = "Set path to NAnt";
                dialog.Filter = "Nant|nant.exe";

                if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    nant.NantPath = dialog.FileName;
                    btnRun.IsEnabled = true;
                    btnSetNant.Visibility = System.Windows.Visibility.Hidden;
                }
            }
        }

        private void btnExport_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                // don't register key presses while doing export
                this.KeyUp -= Window_KeyUp;

                using (var dialog = new SaveFileDialog())
                {
                    dialog.Title = "Export Output to file";
                    dialog.Filter = "Text Files (*.txt)|*.txt";

                    if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    {
                        File.WriteAllText(dialog.FileName, txtOutput.Text);
                    }
                }
            }
            finally
            {
                this.KeyUp += Window_KeyUp;
            }
        }

        private void TargetSelectionChanged(object sender, TargetSelectionChangedEventArgs e)
        {
            if (nant.Targets.Contains(e.Target))
                nant.Targets.Remove(e.Target);
            else
                nant.Targets.Add(e.Target);

            UpdateStatus(StatusType.Update, "Current Targets: " + nant.CurrentTargetNames);
        }

        private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var scroll = (ScrollViewer)sender;

            if (!scroll.IsFocused)
                scroll.ScrollToBottom();
        }

        private void PropertyRemoved(object sender, PropertyRemovedEventArgs e)
        {
            if (nant.Properties.Contains(e.Property))
                nant.Properties.Remove(e.Property);

            for (int i = 0; i < stkProperties.Children.Count; i++)
            {
                PropertyView view = stkProperties.Children[i] as PropertyView;

                if (view != null && view.Property.Equals(e.Property))
                {
                    stkProperties.Children.RemoveAt(i);

                    if (nant.Properties.Contains(e.Property))
                        nant.Properties.Remove(e.Property);

                    UpdateStatus(StatusType.Update, "Removed Property: " + e.Property.ToString());
                    break;
                }
            }
        }

        private void btnAddProperty_Click(object sender, RoutedEventArgs e)
        {
            AddProperty(txtPropertyName.Text.Trim(), txtPropertyValue.Text.Trim());
            // focus the property name after - might be convenient?
            txtPropertyName.Focus();
        }

        private void Window_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
        {
            // only register enter on key press while enter properties -> add property
            if (txtPropertyValue.IsFocused || txtPropertyName.IsFocused || btnAddProperty.IsFocused)
            {
                if (e.Key == System.Windows.Input.Key.Enter)
                    btnAddProperty_Click(this, new RoutedEventArgs());

                return;
            }

            try
            {
                var result = keyHandler.HandleKeyPress(e.Key);

                switch (result)
                {
                    case KeyPressResult.Run:
                        btnRun_Click(this, new RoutedEventArgs());
                        break;
                    case KeyPressResult.Ready:
                        ToggleTarget(keyHandler.PressedValue);
                        break;
                    case KeyPressResult.MoveHighlightDown:
                    case KeyPressResult.MoveHighlightUp:
                        MoveHighlight(result == KeyPressResult.MoveHighlightUp);
                        break;
                    case KeyPressResult.ChangeView:
                        GoToNextView();
                        e.Handled = true;
                        break;
                    case KeyPressResult.ToggleSelect:
                        ToggleHighlightedTarget();
                        break;
                }
            }
            catch (InvalidCastException ex)
            {
                UpdateStatus(StatusType.InputError, ex.Message);
            }
        }

        #endregion

        #region LoadWorker Events
        void loadWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                parsedFile = new NantFile((string)e.Argument);
            }
            catch
            {
                parsedFile = null;
            }
        }

        void loadWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            pbProgress.Visibility = System.Windows.Visibility.Hidden;
            ToggleButtons(true);

            if (parsedFile == null)
            {
                UpdateStatus(StatusType.FileError, "Error parsing nant file.");
                return;
            }

            parsedFile.Targets.Sort();
            // maybe this should be in DoWork??
            for (int i = 0; i < parsedFile.Targets.Count; i++)
            {
                TargetView view = new TargetView(i + 1, parsedFile.Targets[i]);
                view.SelectionChanged += TargetSelectionChanged;
                stkTargets.Children.Add(view);
            }

            if (stkTargets.Children.Count == 0)
                UpdateStatus(StatusType.FileError, "No valid targets found.");
            else
            {
                UpdateStatus(StatusType.Update, "Load completed.");
                lastHighlightIndex = -1;
            }
        }
        #endregion

        #region RunWorker Events
        void runWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            pbProgress.Visibility = System.Windows.Visibility.Hidden;
            ToggleButtons(true);

            if(errorFound)
                UpdateStatus(StatusType.Update, "Execution completed with errors.");
            else
                UpdateStatus(StatusType.Update, "Execution completed.");
        }

        void runWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            nant.Run((string)e.Argument);
        }
        #endregion

        #region Helpers

        void nant_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (!string.IsNullOrEmpty(e.Data))
                UpdateOutput("ERROR: " + e.Data);
        }

        void nant_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            UpdateOutput(e.Data);
        }

        private void GoToNextView()
        {
            // go to the next tab - with wrapping
            Tabs.SelectedIndex = Tabs.SelectedIndex == (Tabs.Items.Count - 1) ? 0 : Tabs.SelectedIndex + 1;
        }
        
        private void MoveHighlight(bool up)
        {
            var targets = FindTargets();
            
            if (targets.Count == 0)
                return;

            int nextIndex = up ? lastHighlightIndex - 1 : lastHighlightIndex + 1;

            if(nextIndex < 0) 
            {
                nextIndex = 0;
            }
            else if (nextIndex > (targets.Count-1))
            {
                nextIndex = targets.Count - 1;
            }

            if (lastHighlightIndex >= 0 && targets[lastHighlightIndex].IsHighlighted)
                targets[lastHighlightIndex].ToggleHighlight();

            if(!targets[nextIndex].IsHighlighted)
                targets[nextIndex].ToggleHighlight();

            lastHighlightIndex = nextIndex;
        }

        private void ToggleHighlightedTarget()
        {
            var view = FindHightlightedView();

            if (view != null)
                view.ChangeCheck();
            else
                UpdateStatus(StatusType.InputError, "Unable to find a highlighted target.");
        }

        private List<TargetView> FindTargets()
        {
            List<TargetView> targets = new List<TargetView>();

            foreach (var child in stkTargets.Children)
            {
                TargetView view = child as TargetView;
                if (view != null)
                    targets.Add(view);
            }

            return targets;
        }

        private TargetView FindHightlightedView()
        {
            if (lastHighlightIndex < 0)
                return null;

            return FindTargets()[lastHighlightIndex];
        }

        private void ToggleTarget(int targetPosition)
        {
            foreach (var child in stkTargets.Children)
            {
                TargetView view = child as TargetView;

                if (view != null && targetPosition == view.Position)
                {
                    view.ChangeCheck();
                    return;
                }
            }

            UpdateStatus(StatusType.InputError, "Unable to find target #" + targetPosition.ToString());
        }

        private void AddProperty(string name, string value)
        {
            try
            {
                Property prop = new Property(name, value);

                if (nant.Properties.Contains(prop))
                {
                    UpdateStatus(StatusType.InputError, "Property already exists.");
                    return;
                }

                nant.Properties.Add(prop);

                PropertyView view = new PropertyView(prop);
                view.Removed += PropertyRemoved;
                stkProperties.Children.Add(view);

                txtPropertyName.Text = "";
                txtPropertyValue.Text = "";

                UpdateStatus(StatusType.Update, "Added Property: " + prop.ToString());
            }
            catch (InvalidOperationException ex)
            {
                UpdateStatus(StatusType.InputError, ex.Message);
            }
        }

        private void LoadTargets()
        {
            nant.Targets.Clear();
            stkTargets.Children.Clear();
            string nantFile = txtNantFile.Text;

            if (!string.IsNullOrEmpty(nantFile))
            {
                if (!File.Exists(nantFile))
                {
                    UpdateStatus(StatusType.InputError, "File not found.");
                    return;
                }

                ToggleButtons(false);
                UpdateStatus(StatusType.Update, "Loading file...");

                pbProgress.Visibility = System.Windows.Visibility.Visible;
                loadWorker.RunWorkerAsync(nantFile);
            }
            else
                UpdateStatus(StatusType.InputError, "No file selected.");
        }

        enum StatusType
        {
            LoadError,
            InputError,
            FileError,
            RunError,
            Update
        }

        private void UpdateOutput(string data)
        {
            if (!txtOutput.CheckAccess())
            {
                var d = new UpdateOutputDelegate(UpdateOutput);
                txtOutput.Dispatcher.BeginInvoke(d, new object[] { data });
            }
            else
            {
                if (NantExec.HasError(data) && !errorFound)
                {
                    errorFound = true;
                    brdrOutput.BorderBrush = System.Windows.Media.Brushes.Maroon;
                }

                txtOutput.AppendText(Environment.NewLine + data);
            }
        }

        private void UpdateStatus(StatusType type, string msg)
        {
            string typeMsg = "";
            switch (type)
            {
                case StatusType.LoadError:
                    typeMsg = "Cannot Load: ";
                    break;
                case StatusType.FileError:
                    typeMsg = "File Error: ";
                    break;
                case StatusType.InputError:
                    typeMsg = "Validation Error: ";
                    break;
                case StatusType.RunError:
                    typeMsg = "NAnt Execution Error: ";
                    break;
            }

            txtStatus.Text = typeMsg + msg;
        }

        private void ToggleButtons(bool enabled)
        {
            btnBrowse.IsEnabled = enabled;
            btnRun.IsEnabled = enabled && nant.HasValidPath;
        }

        #endregion

    }
}
